#ifndef SRC_BASE_THREAD_UTIL_H
#define SRC_BASE_THREAD_UTIL_H

#include "macro_def.h"
#include <sys/time.h>

class Mutex {
  public:
    Mutex() {
      ::pthread_mutex_init(&mutex_, NULL);
    }

    ~Mutex() {
      ::pthread_mutex_destroy(&mutex_);
    }

    void Lock() {
      int ret = ::pthread_mutex_lock(&mutex_);
      PCHECK(ret == 0) << "pthread_mutex_lock error";
    }

    void Unlock() {
      int ret = ::pthread_mutex_unlock(&mutex_);
      PCHECK(ret == 0) << "pthread_mutex_unlock error";
    }

    pthread_mutex_t* mutex() const {
      return &mutex_;
    }

  private:
    pthread_mutex_t mutex_;

    DISALLOW_COPY_AND_ASSIGN(Mutex);
};

class ScopedMutex {
  public:
    explicit ScopedMutex(Mutex* mutex)
        : mutex_(mutex) {
      CHECK_NOTNULL(mutex);
      mutex_->Lock();
    }

    ~ScopedMutex() {
      mutex_->Unlock();
    }

  private:
    Mutex* mutex_;

    DISALLOW_COPY_AND_ASSIGN(ScopedMutex);
};

class RwLock {
  public:
    RwLock() {
      ::pthread_rwlock_init(&rw_lock_, NULL);
    }

    ~RwLock() {
      ::pthread_rwlock_destroy(&rw_lock_);
    }

    void ReadLock() {
      int ret = ::pthread_rwlock_rdlock(&rw_lock_);
      PCHECK(ret == 0) << "pthread_rwlock_rdlock error";
    }

    void WriteLock() {
      int ret = ::pthread_rwlock_wrlock(&rw_lock_);
      PCHECK(ret == 0) << "pthread_rwlock_wrlock error";
    }

    void Unlock() {
      PCHECK(::pthread_rwlock_destroy(&rw_lock_) == 0);
    }

  private:
    pthread_rwlock_t rw_lock_;

    DISALLOW_COPY_AND_ASSIGN(RwLock);
};

class ScopedReadLock {
    explicit ScopedReadLock(RwLock* lock)
        : lock_(lock) {
      CHECK_NOTNULL(lock);
      lock_->ReadLock();
    }

    ~ScopedReadLock() {
      lock_->Unlock();
    }

  private:
    RwLock* lock_;

    DISALLOW_COPY_AND_ASSIGN(ScopedReadLock);
};

class ScopedWriteLock {
    explicit ScopedWriteLock(RwLock* lock)
        : lock_(lock) {
      CHECK_NOTNULL(lock);
      lock_->WriteLock();
    }

    ~ScopedWriteLock() {
      lock_->Unlock();
    }

  private:
    RwLock* lock_;

    DISALLOW_COPY_AND_ASSIGN(ScopedWriteLock);
};

class SyncEvent {
  public:
    SyncEvent()
        : is_signaled_(false) {
      ::pthread_cond_init(&cond_, NULL);
    }

    ~SyncEvent() {
      ::pthread_cond_destroy(&cond_);
    }

    void Wait() {
      ScopedMutex l(&mutex_);
      if (is_signaled_) {
        is_signaled_ = false;
        return;
      }

      int ret = ::pthread_cond_wait(&cond_, mutex_.mutex());
      PCHECK(ret == 0) << "pthread_cond_wait error";
      CHECK(is_signaled_);
    }

    // return false if timeout.
    bool TimeWait(uint32_t msec) {
      {
        ScopedMutex l(&mutex_);
        if (is_signaled_) {
          is_signaled_ = false;
          return true;
        }
      }
      struct timespec ts = { 0 };
      struct timeval tv = { 0 };

      ::gettimeofday(&tv, NULL);
      ts.tv_nsec = tv.tv_usec * 1000 + msec * 1000 * 1000;

      ts.tv_sec += tv.tv_sec + ts.tv_nsec / kNanoPerSec;
      ts.tv_nsec = ts.tv_nsec % kNanoPerSec;

      ScopedMutex l(&mutex_);
      int ret = ::pthread_cond_timedwait(&cond_, mutex_.mutex(), &ts);
      if (ret == ETIMEDOUT) return false;
      CHECK(is_signaled_);
      is_signaled_ = false;
      return true;
    }

    void Signal() {
      ScopedMutex l(&mutex_);
      if (is_signaled_) return;

      ::pthread_cond_signal(&cond_);
      is_signaled_ = true;
    }

  private:
    pthread_cond_t cond_;
    Mutex mutex_;

    bool is_signaled_;
    const static uint32_t kNanoPerSec = 1000UL * 1000 * 1000;

    DISALLOW_COPY_AND_ASSIGN(SyncEvent);
};

#endif
